#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <pthread.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <time.h> 


#include "ServeurTasRepartie.h"

extern int booleanTasInitie;
extern server* serverActuelle;

#define XMALLOC(e) do{if(!(e=malloc(sizeof(*e)))){fprintf(stderr,"Malloc error");exit(1);}}while(0)

int init_data()
{
	return 0;
}

int t_malloc(int size, char* nomVariable)
{
	printf("Size : %d, Nom : %s\n", size, nomVariable);
	return 0;
}

void* t_access_write(char* nomVariable)
{
	printf("Nom : %s\n", nomVariable);
	return NULL;
}

void* t_access_read(char* nomVariable)
{
	printf("Nom : %s\n", nomVariable);
	return NULL;
}

int t_release(char* nomVariable)
{
	printf("Nom : %s\n", nomVariable);
	return 0;
}

int t_free(char* nomVariable)
{
	printf("Nom : %s\n", nomVariable);
	return 0;
}

/* Fin des structure concernant la donnee elle meme */

/* Fonction de gestion des structure local */

/* Creation des structures */
server* newServer(){
	server *s;
    XMALLOC(s);
    s->donneeServer = newDataServerList();
	s->debutTas = NULL;
	s->tailleTas = 0;
	s->espaceLibre = newFreeSpaceList();
	s->listClientConnecte = newListClientConnecte();
	pthread_mutex_init(&(s->lockServer), NULL);

    return s;
}

listClientConnecte* newListClientConnecte(){
	listClientConnecte *lcc;
	XMALLOC(lcc);
	pthread_mutex_init(&(lcc->lockListClient), NULL);
	lcc->size = 0;
	lcc->listClientConnecteElement = newListClientConnecteElement();

	return lcc;
}

void addListClientConnecteElementToList(listClientConnecteElement* lcce){
	serverActuelle->listClientConnecte->size++;
	lcce->next = serverActuelle->listClientConnecte->listClientConnecteElement;
	serverActuelle->listClientConnecte->listClientConnecteElement = lcce;
	return;
}

listClientConnecteElement* newListClientConnecteElement(){
	listClientConnecteElement* lcce;
	XMALLOC(lcce);
	lcce->next = NULL;
	lcce->descriptor = newDescriptorListClientConnecteElement();
	return lcce;
}

listClientConnecteElement* addListClientConnecteElement(descriptorListClientConnecteElement* dlcce){
	listClientConnecteElement* lcce = newListClientConnecteElement();
	lcce->descriptor = dlcce;
	lcce->next = NULL;
	return lcce;
}

descriptorListClientConnecteElement* newDescriptorListClientConnecteElement(){
	descriptorListClientConnecteElement* dlcce;
	XMALLOC(dlcce);
	dlcce->nbThread = 0;
	dlcce->socketID = 0;
	return dlcce;
}

descriptorListClientConnecteElement* addDescriptorListClientConnecteElement(int socketID, pthread_t nbThread){
	descriptorListClientConnecteElement* dlcce = newDescriptorListClientConnecteElement();
	dlcce->socketID = socketID;
	dlcce->nbThread = nbThread;
	return dlcce;
} 

listClient* newListClient(){
	listClient *lc;
	XMALLOC(lc);
	lc->listElementClient = newListElementClient();
	lc->size = 0;
	return lc;
}


server* initServeur(int tailleTas){
	if(!booleanTasInitie){
		serverActuelle->debutTas = malloc(tailleTas);
		serverActuelle->tailleTas = tailleTas;
		booleanTasInitie = 1;

		/* Espace libre */
		serverActuelle->espaceLibre->size = 1;
		serverActuelle->espaceLibre->first->dfs->debutEspace = serverActuelle->debutTas;
		serverActuelle->espaceLibre->first->dfs->finEspace = serverActuelle->debutTas+tailleTas-1;
		serverActuelle->espaceLibre->first->dfs->taille = tailleTas;
		serverActuelle->espaceLibre->first->next = NULL;
	}
	return serverActuelle;
}

dataServerElement* newDataServerElement(){
	dataServerElement* dse;
	XMALLOC(dse);
	dse->data = newDataDescriptor();
	dse->next = NULL;
	return dse;
}


dataServerList* newDataServerList(){
	dataServerList* dsl;
	XMALLOC(dsl);
	dsl->data = newDataServerElement();
	dsl->size = 0;
	pthread_mutex_init(&(dsl->lockDataList), NULL);

	return  dsl;
}

dataDescriptor* newDataDescriptor(){
	dataDescriptor* dd;
	XMALLOC(dd);
	dd->nameData = "";
	dd->sizeData = 0;
	dd->emplacementData = NULL;
	dd->listAttente = newListClient();
	pthread_mutex_init(&(dd->lockData), NULL);

	return dd;
}

listElementClient* newListElementClient(){
	listElementClient* lec;
	XMALLOC(lec);
	lec->client = newClient();
	lec->next = NULL;

	return lec;
}

client* newClient(){
	client* c;
	XMALLOC(c);
	c->socketID = 0;
	c->wishes = 0;
	return c;
}

client* createClient(int socketID, int wishes){
	client* c = newClient();
	c->socketID = socketID;
	c->wishes = wishes;
	return c;
}

/* Fin des create */

/* Debut des find */

dataDescriptor* findDataDescriptor(char* nomVariable){
	dataServerList* dslTmp = serverActuelle->donneeServer;
	dataServerElement* ddTmp = dslTmp->data;
	int i = 0;
	for(i=0 ; i<serverActuelle->donneeServer->size; i++){
		/* Penser au \0 dans le nom de la variable */
		if(strcmp(ddTmp->data->nameData, nomVariable) == 0){
			return ddTmp->data;
		}
		ddTmp = ddTmp->next;
	}
	return NULL;
}

client* findNextToWork(char* nomVariable){
	dataDescriptor* ddTmp = findDataDescriptor(nomVariable);

	if(ddTmp){
		listClient* lcTmp = ddTmp->listAttente;
		client* cTmp = lcTmp->listElementClient->client;
		/*
		Le premier de la liste est le plus ancien
		Si il n'y pas de client on retournera NULL
		de toute façon.
		*/
		return cTmp;
	}
	return NULL;
}

/* Fin des find */

/* Debut des deletes */

void deleteDataDescriptor(char* nomVariable){
	dataServerList* dslTmp = serverActuelle->donneeServer;
	dataServerElement* dseTmp = dslTmp->data;
	dataServerElement* dseTmpNext = dslTmp->data->next;

	int i = 0;

	/* Pour le premier tour de boucle ensuite ça ne devrait plus rentrer dans ce if */
	if(strcmp(dseTmp->data->nameData, nomVariable) == 0){
		serverActuelle->donneeServer->data = serverActuelle->donneeServer->data->next;
		serverActuelle->donneeServer->size--;
		
		freeSpaceListElement* newFsle = newFreeSpaceListElement();
		newFsle->next = NULL;
		newFsle->dfs = addDescriptorFreeSpace(dseTmp->data->emplacementData, dseTmp->data->emplacementData + dseTmp->data->sizeData-1, dseTmp->data->sizeData);

		addFreeSpaceListElement(newFsle);
		free(dseTmp);
	}

	/* Probleme si il n'y a qu'un element */
	for(i=1 ; i<serverActuelle->donneeServer->size; i++){
		/* Penser au \0 dans le nom de la variable */
		if(strcmp(dseTmpNext->data->nameData, nomVariable) == 0){
			dseTmp->next = dseTmpNext->next;
			serverActuelle->donneeServer->size--;
			
			freeSpaceListElement* newFsle = newFreeSpaceListElement();
			newFsle->next = NULL;
			newFsle->dfs = addDescriptorFreeSpace(dseTmpNext->data->emplacementData, dseTmpNext->data->emplacementData + dseTmpNext->data->sizeData-1, dseTmpNext->data->sizeData);
			addFreeSpaceListElement(newFsle);
			free(dseTmpNext);
			break;
		}
		if(dseTmpNext){
			dseTmp = dseTmp->next;
			dseTmpNext = dseTmpNext->next;			
		}
	}
	return;
}

void deleteFirstClientFromList(listClient *list){
	/* Sachant que le premier est le plus ancien de la liste donc le premier */
	client* firstClient = list->listElementClient->client;
	list->listElementClient = list->listElementClient->next;
	list->size--;
	free(firstClient);
	return;
}

/* Fin des deletes */

/* Debut des updates */

/* La taille du nouveau contenu doit etre egal ou inferieur à l'ancien */
int updateDataDescriptor(char* nameData, void* contentData, unsigned int sizeNewData){
	dataDescriptor* ddToUpdate = findDataDescriptor(nameData);
	if (ddToUpdate && (ddToUpdate->sizeData >= sizeNewData))
	{
		memcpy(ddToUpdate->emplacementData, contentData, sizeNewData);
		ddToUpdate->sizeData = sizeNewData;
		return 1;
	}
	/* Erreur */
	printf("Variable : %s introuvale ou contenu trop grand \n", nameData );
	return 0;
}

void addClientListAttente(listClient *list, client* client){
	int i=0; 
	listClient* lcTmp = list;
	listElementClient* lecTmp = lcTmp->listElementClient;

	listElementClient* newLec = newListElementClient();
	newLec->next = NULL;
	newLec->client = client;

	for(i=0 ; i<list->size ; i++){
		lecTmp = lecTmp->next;
	}

	if(!lecTmp->next){
		lecTmp->next = newLec;
		list->size++;
	}
	return;
}

descriptorFreeSpace* newDescriptorFreeSpace(){
	descriptorFreeSpace* dfs;
	XMALLOC(dfs);
	dfs->debutEspace = NULL;
	dfs->finEspace = NULL;
	dfs->taille = 0;

	return dfs;
}

descriptorFreeSpace* addDescriptorFreeSpace(void* debutEspace, void* finEspace, int taille){
	descriptorFreeSpace* dfs = newDescriptorFreeSpace();
	dfs->debutEspace = debutEspace;
	dfs->finEspace = finEspace;
	dfs->taille = taille;
	return dfs;
}


freeSpaceListElement* newFreeSpaceListElement(){
	freeSpaceListElement* sle;
	XMALLOC(sle);
	sle->dfs = newDescriptorFreeSpace();
	sle->next = NULL;
	return sle;
}

freeSpaceList* newFreeSpaceList(){
	pthread_mutex_t lockFreeList;
	freeSpaceList* fsl;
	XMALLOC(fsl);
	fsl->size = 0;
	fsl->first = newFreeSpaceListElement();
	fsl->lockFreeList = lockFreeList;

	return fsl;

}

/* Se charge d'enregistrer les donnees d'un dataDescriptor et le retourne complet */
dataDescriptor* addDataDescriptor(char* nameData, int sizeData, void* contentData){
	
	dataDescriptor* newdd = newDataDescriptor();
	void* emplacement = findFreeSpaceWithSize(sizeData);

	if(emplacement){
		newdd->sizeData = sizeData;
		newdd->emplacementData = emplacement;
		newdd->nameData = nameData;
		memcpy(newdd->emplacementData, contentData, sizeData);

		insertDataDecriptorInServerData(newdd);
		return newdd;
	}
	else{
		printf("Pas de place disponible trouvé pour : %s \n", nameData);
	}
	return NULL;
}
/* 
	A appeler apres chaque suppression pour fusionner les espace libres 
*/
void optimiseEspaceLibre(){
	int i = 0;
	freeSpaceListElement* fsleTmp = serverActuelle->espaceLibre->first;
	freeSpaceListElement* fsleTmpNext = serverActuelle->espaceLibre->first->next;

	for(i=1 ; i<serverActuelle->espaceLibre->size ; i++){
		if(fsleTmp->dfs->finEspace+1 == fsleTmpNext->dfs->debutEspace){
			fsleTmp->dfs->finEspace = fsleTmpNext->dfs->finEspace;
			fsleTmp->dfs->taille = fsleTmp->dfs->taille + fsleTmpNext->dfs->taille;
			removeFreeSpaceListElement(fsleTmpNext);
			optimiseEspaceLibre();
		}
		if (fsleTmpNext)
		{
			fsleTmp = fsleTmpNext;
			fsleTmpNext = fsleTmpNext->next;
		}
	}
	return;
}

void removeFreeSpaceListElement(freeSpaceListElement* fsle){
	int i=0;
	freeSpaceListElement* fsleTmp = serverActuelle->espaceLibre->first;
	freeSpaceListElement* fsleTmpNext = serverActuelle->espaceLibre->first->next;

	if(fsleTmp == fsle){
		serverActuelle->espaceLibre->first = serverActuelle->espaceLibre->first->next;
		free(fsleTmp);
		serverActuelle->espaceLibre->size--;
	}else{
		for(i=1 ; i<serverActuelle->espaceLibre->size; i++){
			if(fsleTmpNext == fsle){
				fsleTmp->next = fsleTmpNext->next;
				free(fsleTmpNext);
				serverActuelle->espaceLibre->size--;
				break;
			}
			if(fsleTmpNext){
				fsleTmp = fsleTmpNext;
				fsleTmpNext = fsleTmpNext->next;
			}
		}
	}
	return;
}

/* 
	First fit - Best fit - Worst fit ? 
	Pour simplifier le traitement je choisis une logique First fit
	Retourne le pointeur sur l'espace libre
*/
void* findFreeSpaceWithSize(int size){
	int i = 0;
	freeSpaceListElement* fsleTmp = serverActuelle->espaceLibre->first;
	void* emplacementToReturn = NULL;

	for(i=0 ; i<serverActuelle->espaceLibre->size ; i++){
		if(fsleTmp->dfs->taille >= size){
			emplacementToReturn = fsleTmp->dfs->debutEspace;
			fsleTmp->dfs->debutEspace = fsleTmp->dfs->debutEspace + size;
			if(fsleTmp->dfs->debutEspace == fsleTmp->dfs->finEspace){
				removeFreeSpaceListElement(fsleTmp);
				break;
			}
			else{
				fsleTmp->dfs->taille = fsleTmp->dfs->taille - size;
				break;
			}
		}
		if(fsleTmp->next){
			fsleTmp = fsleTmp->next;
		}
	}
	return emplacementToReturn;
}

/* Fonction qui insere un espace libre dans la free liste de maniere ordonnee */
void addFreeSpaceListElement(freeSpaceListElement* fsle){
	int i=0;

	freeSpaceListElement* fsleTmp = serverActuelle->espaceLibre->first;
	freeSpaceListElement* fsleTmpNext = serverActuelle->espaceLibre->first->next;
	
	if(fsle->dfs->finEspace < fsleTmp->dfs->debutEspace){
		fsle->next = fsleTmp;
		serverActuelle->espaceLibre->first = fsle;
		serverActuelle->espaceLibre->size++;
	}
	else{
		for(i=1 ; i<serverActuelle->espaceLibre->size ; i++){
			if(fsle->dfs->finEspace < fsleTmpNext->dfs->debutEspace){
				fsle->next = fsleTmpNext;
				fsleTmp->next = fsle;
				serverActuelle->espaceLibre->size++;
				break;
			}
			if(fsleTmpNext){
				fsleTmp = fsleTmp->next;
				fsleTmpNext = fsleTmpNext->next;
			}
		}
	}

	optimiseEspaceLibre();
	return;
}
/* 
	Insert le dataDescriptor au debut de la liste des donnees du serveur.
	Comme ça les dernieres data creer seront les plus rapides d'acces.

*/
void insertDataDecriptorInServerData(dataDescriptor* dd){
	dataServerElement* newdse = newDataServerElement();

	newdse->data = dd;
	newdse->next = serverActuelle->donneeServer->data;
	serverActuelle->donneeServer->data = newdse;
	serverActuelle->donneeServer->size++;
	return;	
}


/* Gestion  */

/* Fin des updates */


/* Fin des fonctions de gestion des structure local */
/*
int main(int argc, char const *argv[])
{
	printf("Arg : %s\n", argv[argc] );
	initServeur(1000);

	char c = 'q';
	addDataDescriptor("toto_1", 1, &c);

	dataDescriptor* test = findDataDescriptor("toto_1");
	printf("Test : %c adresse %p \n", *((char*)(test->emplacementData)), test->emplacementData);

	c = 'z';
	int testUpdate = updateDataDescriptor("toto_1", &c,1);
	if(testUpdate){
		printf("Test : %c adresse %p \n", *((char*)(test->emplacementData)), test->emplacementData);

	}
	deleteDataDescriptor("toto_1");
	printf("Nombre de donnees %d \n", serverActuelle->donneeServer->size);
	printf("Nb d'espace libre: %d\n", serverActuelle->espaceLibre->size);

	c = 'a';
	addDataDescriptor("toto_2", 1, &c);

	c = 'b';
	addDataDescriptor("toto_3", 1, &c);

	c = 'c';
	addDataDescriptor("toto_4", 1, &c);

	c = 'd';
	addDataDescriptor("toto_5", 1, &c);

	c = 'e';
	addDataDescriptor("toto_6", 1, &c);

	c = 'f';
	addDataDescriptor("toto_7", 1, &c);

	c = 'g';
	addDataDescriptor("toto_8", 1, &c);

	c = 'h';
	addDataDescriptor("toto_9", 1, &c);

	dataDescriptor* test2 = findDataDescriptor("toto_2");

	dataDescriptor* test3 = findDataDescriptor("toto_3");

	dataDescriptor* test4 = findDataDescriptor("toto_4");

	dataDescriptor* test5 = findDataDescriptor("toto_5");

	dataDescriptor* test6 = findDataDescriptor("toto_6");

	dataDescriptor* test7 = findDataDescriptor("toto_7");

	dataDescriptor* test8 = findDataDescriptor("toto_8");

	dataDescriptor* test9 = findDataDescriptor("toto_9");

	printf("Test : %c, adresse %p\n", *((char*)(test2->emplacementData)), test2->emplacementData);

	printf("Test : %c, adresse %p\n", *((char*)(test3->emplacementData)), test3->emplacementData);

	printf("Test : %c, adresse %p\n", *((char*)(test4->emplacementData)), test4->emplacementData);

	printf("Test : %c, adresse %p\n", *((char*)(test5->emplacementData)), test5->emplacementData);

	printf("Test : %c, adresse %p\n", *((char*)(test6->emplacementData)), test6->emplacementData);

	printf("Test : %c, adresse %p\n", *((char*)(test7->emplacementData)), test7->emplacementData);

	printf("Test : %c, adresse %p\n", *((char*)(test8->emplacementData)), test8->emplacementData);

	printf("Test : %c, adresse %p\n", *((char*)(test9->emplacementData)), test9->emplacementData);

	deleteDataDescriptor("toto_2");
	printf("Nombre de donnees %d \n", serverActuelle->donneeServer->size);
	printf("Nb d'espace libre: %d\n", serverActuelle->espaceLibre->size);


	deleteDataDescriptor("toto_4");
	printf("Nombre de donnees %d \n", serverActuelle->donneeServer->size);
	printf("Nb d'espace libre: %d\n", serverActuelle->espaceLibre->size);

	deleteDataDescriptor("toto_6");
	printf("Nombre de donnees %d \n", serverActuelle->donneeServer->size);
	printf("Nb d'espace libre: %d\n", serverActuelle->espaceLibre->size);

	deleteDataDescriptor("toto_8");
	printf("Nombre de donnees %d \n", serverActuelle->donneeServer->size);
	printf("Nb d'espace libre: %d\n", serverActuelle->espaceLibre->size);
	return -1;
}
*/